<?php
/**
 * MySQLi
 *
 * @category	DataSource
 * @package		dayscript.datasource.sql.statement
 * @subpackage
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license
 * @version		1.0
 * @version		$Revision: 0 $
 * @filesource
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses
 * @since		1.0
 * @modifiedby	$LastChangedBy: Nelson Daza $
 * @modified	$Date: 2009-09-02 $
 */

namespace dayscript\datasource\sql\statement;

/**
 * MySQLi
 *
 * @category	DataSource
 * @package		dayscript.datasource.sql.statement
 * @subpackage
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license
 * @version		1.0
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses		\dayscript\datasource\sql\Statement
 * @since		1.0
 * @modifiedby	$LastChangedBy: Nelson Daza $
 * @modified	$Date: 2009-09-02 $
 */
use dayscript\util\Debug;

class MySQLi extends \dayscript\datasource\sql\Statement	{
	/**
	 * The MySQLi_stmt object.
	 * @var MySQLi_stmt $stmt
	 */
	protected $stmt;
	/**
	 * Column names.
	 * @var array $keys
	 */
	protected $keys;
	/**
	 * Fetched result values.
	 * @var array $values
	 */
	protected $values;
    /**
     * Statement metadata
     * @var array $meta
     * TODO Type?
     */
    protected $meta = null;
	/**
	 * @param  string $sql
	 */
	public function prepare( $sql )	{
		$MySQLi = $this->adapter->getConnection( );
		$this->stmt = $MySQLi->prepare( $sql );
		if ( $this->stmt === false || $MySQLi->errno )
			throw new \Exception( 'MySQLi prepare error: (' . $MySQLi->errno . ') ' . $MySQLi->error . ' IN: ' . $sql );
	}
	/**
	 * @see \dayscript\datasource\sql\Statement
	 */
	protected function statementBindParam( $parameter, &$variable, $type = null, $length = null, $options = null )	{
		return true;
	}
	/**
	 * @see \dayscript\datasource\sql\Statement
	 */
	public function close( )	{
		if ( $this->stmt )	{
			$result = $this->stmt->close( );
			$this->stmt = null;
			return $result;
		}
		return false;
	}
	/**
	 * @see \dayscript\datasource\sql\Statement
	 */
	public function closeCursor( )	{
		if ( $this->stmt )	{
			$MySQLi = $this->adapter->getConnection( );
			while ( $MySQLi->more_results( ) )
				$MySQLi->next_result( );
			$this->stmt->free_result( );
			return $this->stmt->reset( );
		}
		return false;
	}
	/**
	 * @see \dayscript\datasource\sql\Statement
	 */
	public function columnCount( )	{
		if ( $this->meta )
			return $this->meta->field_count;
		return 0;
	}
	/**
	 * @see \dayscript\datasource\sql\Statement
	 */
	public function rowCount( )	{
		if ( !$this->adapter )
			return 0;
		$MySQLi = $this->adapter->getConnection( );
		return $MySQLi->affected_rows;
	}
	/**
	 * @see \dayscript\datasource\sql\Statement
	 */
	public function errorCode( )	{
		if ( !$this->stmt )
			return null;
		return \substr( $this->stmt->sqlstate, 0, 5 );
	}
	/**
	 * @see \dayscript\datasource\sql\Statement
	 */
	public function errorInfo( )	{
		if ( !$this->stmt )
			return null;
		return array(
			\substr( $this->stmt->sqlstate, 0, 5 ),
			$this->stmt->errno,
			$this->stmt->error,
		);
	}
	/**
	 * @see \dayscript\datasource\sql\Statement
	 */
	public function statementExecute( array $params = null )	{
		if ( !$this->stmt )
			return false;

		// if no params were given as an argument to execute(), then default to the bindParam array
		if ( $params === null )
			$params = $this->bindParam;
		
		// send $params as input parameters to the statement
		if ( $params )	{
			\array_unshift( $params, \str_repeat( 's', \count( $params ) ) );
			$stmtParams = array( );
			foreach ( $params as $k => &$value )
				$stmtParams[$k] = &$value;
			\call_user_func_array( array( $this->stmt, 'bind_param' ), $stmtParams );
		}

		$retval = $this->stmt->execute( );
		if ( $retval === false )
			throw new \Exception( 'MySQLi statement execute error: (' . $this->stmt->errno . ') ' . $this->stmt->error );

		// retain metadata
		if ( $this->meta === null )	{
			$this->meta = $this->stmt->result_metadata( );
			if ( $this->stmt->errno )
				throw new \Exception( 'MySQLi statement metadata error: (' . $this->stmt->errno . ') ' . $this->stmt->error );
		}

		// statements that have no result set do not return metadata
		if ( $this->meta !== false )	{

			// get the column names that will result
			$this->keys = array( );
			foreach ( $this->meta->fetch_fields( ) as $col )
				$this->keys[] = $col->name;

			// set up a binding space for result variables
			$this->values = \array_fill( 0, \count( $this->keys ), null );

			// set up references to the result binding space.
			// just passing $this->values in the call_user_func_array( ) below won't work, you need references.
			$refs = array( );
			foreach ( $this->values as $i => &$f )
				$refs[$i] = &$f;

			$this->stmt->store_result( );
			
			// bind to the result variables
			\call_user_func_array( array( $this->stmt, 'bind_result' ), $this->values );
		}
		return $retval;
	}
	/**
	 * @see \dayscript\datasource\sql\Statement
	 */
	public function fetch( $style = null, $cursor = null, $offset = null )	{
		if ( !$this->stmt )
			return false;
		
		// fetch the next result
		$retval = $this->stmt->fetch( );
		switch ( $retval )	{
			case null:		// end of data
			case false:		// error occurred
				$this->stmt->reset( );
				return $retval;
			default:		// fallthrough
				;
		}

		// make sure we have a fetch mode
		if ( $style === null )
			$style = $this->fetchMode;

		// dereference the result values, otherwise things like fetchAll() return the same values for
		// every entry (because of the reference).
		$values = array( );
		foreach ( $this->values as $key => $val )
			$values[] = $val;

		$row = null;
		switch ( $style )	{
			case \dayscript\datasource\sql\Database::FETCH_NUM:
				$row = $values;
				break;
			case \dayscript\datasource\sql\Database::FETCH_ASSOC:
				$row = \array_combine( $this->keys, $values );
				break;
			case \dayscript\datasource\sql\Database::FETCH_BOTH:
				$assoc = \array_combine( $this->keys, $values );
				$row = \array_merge( $values, $assoc );
				break;
			case \dayscript\datasource\sql\Database::FETCH_OBJ:
				$row = (object)\array_combine( $this->keys, $values );
				break;
			case \dayscript\datasource\sql\Database::FETCH_BOUND:
				$assoc = \array_combine( $this->keys, $values );
				$row = \array_merge( $values, $assoc );
				return $this->fetchBound( $row );
				break;
			default:
				throw new \InvalidArgumentException( 'Invalid fetch mode \'' . $style . '\' specified.' );
				break;
		}
		return $row;
	}
	/**
	 * @see \dayscript\datasource\sql\Statement
	 */
	public function nextRowset( )	{
		throw new \Exception( __FUNCTION__ . '() is not implemented.' );
	}
}
